package it.polito.let.http;

import it.polito.let.pages.FactoryException;
import it.polito.let.pages.Page;
import it.polito.let.pages.PageFactory;
import it.polito.let.pages.PageNotFoundException;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.logging.Logger;

public class ClientHandler extends Thread {
	private Socket s;
	private DataInputStream in;

	public ClientHandler(Socket newso) throws IOException {
		s = newso;
		in = new DataInputStream(s.getInputStream());
	}

	public void run() {
		PageFactory factory;
		Request clientRequest;
		Response serverResponse;

		boolean closeConnection = false;
		try {
			while (!closeConnection) {
				try {
					clientRequest = readClientRequest();
					if (clientRequest == null)
						continue; // ignore newlines at start

					closeConnection = clientRequest
					.containsHeaderField("Connection")
					&& clientRequest.getHeaderField("Connection")
					.equals("close");

					// Instatiating the factory
					factory = PageFactory.getInstance();
					// Retrieving the page
					Page page = factory.getPage(clientRequest);
					page.handleRequest();
					serverResponse = page.getResponse();
				} catch (PageNotFoundException pnfe) {
					HTTPError httpe = new HTTPError((short) 404, "Not Found");
					httpe.addAdditionalInfo(pnfe.getMessage());
					serverResponse = httpe.getResponse();
				} catch (FactoryException fe) {
					HTTPError httpe = new HTTPError((short) 500, "Server Error");
					httpe.addAdditionalInfo(fe.getMessage());
					serverResponse = httpe.getResponse();
				} catch (HTTPError e) {
					serverResponse = e.getResponse();
				}

				if (serverResponse == null)
					serverResponse = new HTTPError((short) 500, "Server Error")
				.getResponse();

				closeConnection = (serverResponse
						.containsHeaderField("Connection") && serverResponse
						.getHeaderField("Connection").equals("close"))
						|| closeConnection;
				sendServerResponse(serverResponse);

				if (closeConnection) {
					break;
				}
			}
		} catch (EOFException e) {
			// EOF, do nothing
		} catch (IOException e) {
		} finally {
			try {
				Logger.getLogger("it.polito.let.jhpserver").info(
						"HTTP Server: Closing Socket with client "
						+ s.getInetAddress().toString());
				s.close();
			} catch (IOException e) {
				Logger.getLogger("it.polito.let.jhpserver").warning(
				"HTTP Server: Socket Close failed, keep going on.");
			}
		}
	}

	private void sendServerResponse(Response serverResponse) throws IOException {
		DataOutputStream clientOutput = new DataOutputStream(s
				.getOutputStream());
		InputStream dataInput;
		String strBuffer;
		Set<String> headerFieldNames = serverResponse.getHeaderFieldNames();
		byte[] buffer;
		int bytesRead;

		strBuffer = serverResponse.getHttpVersion() + " "
		+ serverResponse.getResponseCode() + " "
		+ serverResponse.getResponseMessage() + HTTPConstants.CRLF;
		clientOutput.write(strBuffer.getBytes());

		for (String headerFieldName : headerFieldNames) {
			strBuffer = headerFieldName + ": "
			+ serverResponse.getHeaderField(headerFieldName)
			+ HTTPConstants.CRLF;
			clientOutput.write(strBuffer.getBytes());
		}

		clientOutput.write(HTTPConstants.CRLF.getBytes());
		clientOutput.flush();

		dataInput = serverResponse.getPayloadStream();
		if (dataInput == null)
			return;

		buffer = new byte[HTTPConstants.PACCHETTO];
		while ((bytesRead = dataInput.read(buffer)) != -1) {
			clientOutput.write(buffer, 0, bytesRead);
		}
		clientOutput.flush();
	}

	private Request readClientRequest() throws IOException, HTTPError {
		String msgHeaderLine = "";
		RequestBuilder requestBuilder;
		Message request = null;
		LineReader reader = new LineReader(in);

		try {
			msgHeaderLine = reader.readLine();
			if (msgHeaderLine.equals(HTTPConstants.CRLF))
				return null; // ignore newlines at start
			requestBuilder = new RequestBuilder(msgHeaderLine);

			for (;;) {
				msgHeaderLine = reader.readLine();
				if (msgHeaderLine.equals(HTTPConstants.CRLF))
					break;
				requestBuilder.parseHeaderLine(msgHeaderLine);
			}

			request = requestBuilder.getMessage();
			request.setPayloadStream(in);
		} catch (HTTPError e) {
			// wait for request end, then throw exception again
			try {
				byte[] buf = new byte[4096];
				while (in.read(buf) != -1)
					;
				throw e;
			} catch (IOException iE) {
				throw new HTTPError((short) 500, "Server Error");
			}
		}
		return (Request) request;
	}

	/*
	private Request readClientRequest() throws IOException, HTTPError {
		HTTPCup parser = new HTTPCup(new HTTPLex(in));
		try {
			parser.parse();
		} catch (Exception e) {
			// wait for request end, then throw exception again
			HTTPError httpe = new HTTPError((short) 400, "Bad Request");
			httpe.addAdditionalInfo(e.getMessage());
			try {
				byte[] buf = new byte[4096];
				while (in.read(buf) != -1);
				throw httpe;
			} catch (IOException iE) {
				throw new HTTPError((short) 500, "Server Error");
			}
		}
		return null;
	}
	 */
}
